// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
#pragma once
#include <DirectXMath.h>

namespace Weathr
{
    // Defines the latitude and longitude coordinates on a sphere.
    struct LatLon
    {
        LatLon() : LatLon(0.0f, 0.0f) {}
        LatLon(float latitude, float longitude) : Latitude(latitude), Longitude(longitude) {}

        float Latitude;   // Pole-to-pole coordinate on the sphere, in degrees.
        float Longitude;  // Left-right coordinate on the sphere, in degrees.

        // Converts sphere coordinates to a LatLon object.
        static LatLon XM_CALLCONV FromSphereCoordinates(DirectX::FXMVECTOR sphereCoords)
        {
            const auto x = DirectX::XMVectorGetX(sphereCoords);
            const auto y = DirectX::XMVectorGetY(sphereCoords);
            const auto z = DirectX::XMVectorGetZ(sphereCoords);

            const auto longitude = atan2f(x, z);
            const auto r = hypotf(x, z);
            const auto latitude = atan2f(y, r);
            return LatLon(DirectX::XMConvertToDegrees(latitude), DirectX::XMConvertToDegrees(longitude));
        }

        // Converts this object to sphere coordinates.
        DirectX::XMVECTOR ToSphereCoordinates(float radius) const
        {
            const auto latitude = DirectX::XMConvertToRadians(Latitude);
            const auto longitude = DirectX::XMConvertToRadians(Longitude);
            const auto tz = radius * cos(latitude) * cos(longitude);
            const auto ty = radius * sin(latitude);
            const auto tx = radius * cos(latitude) * sin(longitude);
            DirectX::XMFLOAT3 result(tx, ty, tz);
            return DirectX::XMLoadFloat3(&result);
        }

        // Converts this object to screen coordinates.
        DirectX::XMFLOAT2 XM_CALLCONV ToScreenCoordinates(float radius, const D3D11_VIEWPORT& viewport, DirectX::FXMMATRIX world, DirectX::FXMMATRIX view, DirectX::FXMMATRIX projection) const
        {
            const DirectX::XMVECTOR sphereCoords = ToSphereCoordinates(radius);
            DirectX::XMVECTOR v = DirectX::XMVector3Project(sphereCoords, viewport.TopLeftX, viewport.TopLeftY, viewport.Width, viewport.Height, viewport.MinDepth, viewport.MaxDepth, projection, view, world);
            DirectX::XMFLOAT2 result;
            result.x = DirectX::XMVectorGetX(v);
            result.y = DirectX::XMVectorGetY(v);
            return result;
        }
    };

    // Below is adapted from http://blog.julien.cayzac.name/2008/10/arc-and-distance-between-two-points-on.html

    /// Earth's quatratic mean radius for WGS-84 
    static const float EARTH_RADIUS_IN_METERS = 6372797.560856f;
    static const float EARTH_RADIUS_IN_FEET = 20908128.5f;

    // Computes the arc, in radian, between two WGS-84 positions.
    inline double ArcInRadians(const LatLon& from, const LatLon& to)
    {
        float latitudeArc = DirectX::XMConvertToRadians(from.Latitude - to.Latitude);
        float longitudeArc = DirectX::XMConvertToRadians(from.Longitude - to.Longitude);
        float latitudeH = sin(latitudeArc * 0.5f);
        latitudeH *= latitudeH;
        float lontitudeH = sin(longitudeArc * 0.5f);
        lontitudeH *= lontitudeH;
        float tmp = cos(DirectX::XMConvertToRadians(from.Latitude)) * cos(DirectX::XMConvertToRadians(to.Latitude));
        return 2.0f * asin(sqrt(latitudeH + tmp*lontitudeH));
    } 

    // Computes the distance, in meters, between two WGS-84 positions.
    inline double DistanceInMeters(const LatLon& from, const LatLon& to)
    {
        return EARTH_RADIUS_IN_METERS*ArcInRadians(from, to);
    }

    inline double DistanceInFeet(const LatLon& from, const LatLon& to)
    {
        return EARTH_RADIUS_IN_FEET*ArcInRadians(from, to);
    }
}